<?php
abstract class Kbs_Model_Mapper_Abstract
{
    // Must be assigned within instance
    protected $_tableClassName = '';

    protected $_table = null;

    /**
     * The data mapper map array
     */
    //protected $_dataMapper = array();

    /**
     * Specify Kbs_Model_Table_Abstract instance to use for data operations
     * 
     * @param  Kbs_Model_Table_Abstract|string $table 
     * @return Kbs_Model_Mapper_Abstract
     */
    public function setTable($table)
    {
        if (is_object($table) and $table instanceof Zend_Db_Table_Abstract) {
            $this->_table = $table;
        } elseif (is_string($table) and class_exists($table)) {
            $this->_table = new $table();
        } else {
            throw new Exception('Table should be instance of '
                               .'Zend_Db_Table_Abstract or class name of '
                               .'Zend_Db_Table_Abstract !');
        }
        return $this;
    }

    /**
     * Get registered Zend_Db_Table instance
     *
     * Lazy loads Kbs_Model_Table_Abstract if no instance registered
     * 
     * @return Kbs_Model_Table_Abstract
     */
    public function getTable()
    {
        if (null === $this->_table) {
            if (empty($this->_tableClassName) or !class_exists($this->_tableClassName)) {
                throw new Exception('Table class name must be assigned within ' . get_class($this) . ' !');
            }
            $this->setTable($this->_tableClassName);
        }
        return $this->_table;
    }

    /**
     * Find a entry by id
     * 
     * @param  int|array $id 
     * @param  Kbs_Model_Business_Abstract $businessObj 
     * @return void
     */
    public function find($id, Kbs_Model_Business_Abstract $businessObj)
    {
        $result = $this->getTable()->find($id);
        if (0 == count($result)) {
            return;
        }
        $row = $result->current();

        $this->setData($businessObj, $row);
    }

    /**
     * Fetch all entries
     * 
     * @param string $businessClassName
     * @param string|null $where
     * @return array|false array of Kbs_Model_Business_Abstract
     */
    public function fetchAll($businessClassName, $where = null)
    {
        $resultSet = $this->getTable()->fetchAll($where);

        if ($resultSet->count()) {
            $result = array();
            foreach ($resultSet as $row) {
                $entry = new $businessClassName();
                $this->setData($entry, $row);
                $result[] = $entry;
            }    
            return $result;
        } else {
            return false;
        }
    }

    /**
     * Save an entry
     * 
     * @param  Kbs_Model_Business_Abstract $businessObj
     * @return array|int $newId
     */
    public function save(Kbs_Model_Business_Abstract $businessObj)
    {
        foreach ($this->_dataMapper as $objKey => $tableRowField) {
            if (null !== $businessObj->$objKey) {
                $data[$tableRowField] = $businessObj->$objKey;
            }
        }
        
        $id = $businessObj->getId();
        $idKeys = $this->getTable()->info(Zend_Db_Table::PRIMARY);

        if (empty($id)) {
            foreach ($idKeys as $key) {
                unset($data[$key]);
            }
            return $this->getTable()->insert($data);
        } else {
            foreach ($idKeys as $key) {
                //$where[$key] = $data[$key];
                $where[] = "$key = {$data[$key]}";
                unset($data[$key]);
            }
            $where = implode(' AND ', $where);
            $this->getTable()->update($data, $where);
            return $id;
        }
    }

    /**
     * Delete a record
     * 
     * @param  Kbs_Model_Business_Abstract $businessObj
     * @return array|int $id
     */
    public function delete(Kbs_Model_Business_Abstract $businessObj)
    {
        $id = $businessObj->getId();
        $idKeys = $this->getTable()->info(Zend_Db_Table::PRIMARY);
        if (empty($id)) {
            throw new Exception('Ids should be setted before deleting a record');
        } else {
            $where = "{$idKeys[1]} = $id";
            $this->getTable()->delete($where);
            return $id;
        }
    }

    /**
     * Overloading: implements accessible methods 
     *
     * @code $this->findCountByDetailIdAndCategoryId($detailId, $categoryId);
     * @code $this->findByDetailIdAndCategoryId($detailId, $categoryId);
     *
     * @param string $name
     * @param array $arguments
     * @param string $businessObj
     * @return int|array
     */
    public function __call($name, $arguments)
    {
        $businessObj = array_pop($arguments);

        // Find count by property
        if (false !== strrpos($name, 'findCountBy')) {

            $function = str_replace('findCountBy', 'findBy', $name);
            $result = $this->__call($function, $arguments);
            if ($result) {
                return count($result);
            } else {
                return false;
            }

        } elseif (false !== strrpos($name, 'findByParams')) {

            foreach ($arguments[0] as $property => $value) {
                $where[] = $this->_dataMapper[$property] . ' = ' . $value;
            }
            $where = implode(' And ', $where);
            $entries = $this->fetchAll($businessObj, $where);
            return $entries;

        } elseif (false !== strrpos($name, 'findBy')) {

            $properties = str_replace('findBy', '', $name);

            $where = array();
            foreach (explode('And', $properties) as $key => $property) {
                $char = strtolower(substr($property, 0, 1));
                $property = str_replace(substr($property, 0, 1), $char, $property);
                $where[] = $this->_dataMapper[$property] . ' = ' . $arguments[$key];
            }
            $where = implode(' And ', $where);
            
            $entries = $this->fetchAll($businessObj, $where);
            return $entries;

        } else {
            throw new Exception("Method $name undefined in " . get_class($this) . " !");
        }
    }

    /**
     * To set the data
     * must be implemented in real instance
     *
     * @param Kbs_Model_Business_Abstract $businessObj
     * @param Zend_Db_Table_Row $row
     * @return void
     * @throw Exception
     */
    abstract public function setData(Kbs_Model_Business_Abstract $businessObj, Zend_Db_Table_Row $row);
    /*{
        if (!empty($this->_dataMapper)) {
            foreach ($this->_dataMapper as $objKey => $tableRowField) {
                if (!class_exists($tableRowField)) {
                    $businessObj->$objKey = $row->$tableRowField;
                }
            }
        } else {
            throw new Exception('Data mapper must not be empty !');
        }
    } */
}